package com.katesoft.scale4j.common.utils;

import com.katesoft.scale4j.common.annotation.NullSafe;

import java.util.StringTokenizer;

/**
 * @author Santhosh Kumar T
 * @author kate2007
 */
public class StringUtility
{
    /**
     * if <code>obj</code> is null, returns empty string. otherwise returns <code>obj.toString()</code>
     *
     * @param obj any ref you want
     *
     * @return <code>obj</code> or <code>obj.toString()</code>
     */
    public static String toString(Object obj)
    {
        return obj == null ? "" : obj.toString();
    }

    /**
     * returns true if <code>str</code> is null or its length is zero
     *
     * @param str any char sequence you want
     *
     * @return true if <code>str</code> is null or its length is zero.
     */
    public static boolean isEmpty(CharSequence str)
    {
        return str == null || str.length() == 0;
    }

    /**
     * returns true if <code>str</code> is null or it contains only white spaces.
     * <p/>
     * {@link Character#isWhitespace(char)} is used to test for whitespace
     *
     * @param str any char sequence you want
     *
     * @return true if <code>str</code> is null or it contains only white spaces.
     */
    @NullSafe
    public static boolean isWhitespace(CharSequence str)
    {
        if (str != null) {
            for (int i = 0; i < str.length(); i++) {
                if (!Character.isWhitespace(str.charAt(i))) { return false; }
            }
        }
        return true;
    }

    /**
     * Splits given string into tokens with delimiters specified. It uses StringTokenizer for tokenizing.
     *
     * @param str   string to be tokenized
     * @param delim delimiters used for tokenizing
     * @param trim  trim the tokens
     *
     * @return non-null token array
     */
    public static String[] getTokens(String str,
                                     String delim,
                                     boolean trim)
    {
        StringTokenizer stok = new StringTokenizer(str, delim);
        String tokens[] = new String[stok.countTokens()];
        for (int i = 0; i < tokens.length; i++) {
            tokens[i] = stok.nextToken();
            if (trim) { tokens[i] = tokens[i].trim(); }
        }
        return tokens;
    }

    /**
     * Converts first character in <code>str</code> to uppercase.
     * <p/>
     * This method can be called on string of any length.
     *
     * @param str string to be converted
     *
     * @return string with first letter changed to uppercase
     */
    public static String capitalize(String str)
    {
        if (str == null) { return null; }
        switch (str.length()) {
            case 0:
                return str;
            case 1:
                return str.toUpperCase();
            default:
                return Character.toUpperCase(str.charAt(0)) + str.substring(1);
        }
    }

    /**
     * Makes an underscored form from the expression in the string.
     * <p/>
     * Examples: <pre class="prettyprint">
     * <p/>
     * underscore("activeRecord")   "active_record"
     * <p/>
     * underscore("ActiveRecord")    "active_record"
     * <p/>
     * underscore("firstName")       "first_name"
     * <p/>
     * underscore("FirstName")        "first_name"
     * <p/>
     * underscore("name")              "name" </pre>
     *
     * @param camelCaseWord the camel-cased word that is to be converted;
     *
     * @return a lower-cased version of the input, with separate words delimited by the underscore character.
     */
    public static String underscore(String camelCaseWord)
    {
        if (camelCaseWord == null) { return null; }
        camelCaseWord = camelCaseWord.trim();
        if (camelCaseWord.length() == 0) { return ""; }
        camelCaseWord = camelCaseWord.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2");
        camelCaseWord = camelCaseWord.replaceAll("([a-z\\d])([A-Z])", "$1_$2");
        return camelCaseWord.toLowerCase();
    }

    /**
     * Turns a non-negative number into an ordinal string used to denote the position in an ordered sequence, such as 1st, 2nd, 3rd, 4th
     *
     * @param number the non-negative number
     *
     * @return the string with the number and ordinal suffix
     */
    public static String ordinalize(int number)
    {
        int modulo = number % 100;
        if (modulo >= 11 && modulo <= 13) { return number + "th"; }
        switch (number % 10) {
            case 1:
                return number + "st";
            case 2:
                return number + "nd";
            case 3:
                return number + "rd";
            default:
                return number + "th";
        }
    }

    public static int[] toCodePoints(String str)
    {
        int count = str.codePointCount(0, str.length());
        int[] codePoints = new int[count];
        for (int cpIndex = 0, charIndex = 0; cpIndex < count; cpIndex++) {
            int cp = str.codePointAt(charIndex);
            codePoints[cpIndex] = cp;
            charIndex += Character.charCount(cp);
        }
        return codePoints;
    }

    /*-------------------------------------------------[ Literal ]---------------------------------------------------*/
    public static String toLiteral(char ch,
                                   boolean useRaw)
    {
        if (ch == '\'') { return "\\'"; }
        else if (ch == '"') { return "\""; }
        else { return StringUtility.toLiteral(String.valueOf(ch), useRaw); }
    }

    public static String toLiteral(CharSequence str,
                                   boolean useRaw)
    {
        StringBuffer buf = new StringBuffer(str.length() + 25);
        for (int i = 0, len = str.length(); i < len; i++) {
            char c = str.charAt(i);
            switch (c) {
                case '\b':
                    buf.append("\\b");
                    break;
                case '\t':
                    buf.append("\\t");
                    break;
                case '\n':
                    buf.append("\\n");
                    break;
                case '\f':
                    buf.append("\\f");
                    break;
                case '\r':
                    buf.append("\\r");
                    break;
                case '\"':
                    buf.append("\\\"");
                    break;
                case '\\':
                    buf.append("\\\\");
                    break;
                default:
                    if (c >= 0x0020 && (useRaw || c <= 0x007f)) // visible character in ascii
                    { buf.append(c); }
                    else {
                        buf.append("\\u");
                        String hex = Integer.toHexString(c);
                        for (int j = 4 - hex.length(); j > 0; j--) { buf.append('0'); }
                        buf.append(hex);
                    }
            }
        }
        return buf.toString();
    }

    public static String fromLiteral(String str)
    {
        StringBuffer buf = new StringBuffer();
        for (int i = 0, len = str.length(); i < len; i++) {
            char c = str.charAt(i);
            switch (c) {
                case '\\':
                    if (i == str.length() - 1) {
                        buf.append('\\');
                        break;
                    }
                    c = str.charAt(++i);
                    switch (c) {
                        case 'n':
                            buf.append('\n');
                            break;
                        case 't':
                            buf.append('\t');
                            break;
                        case 'r':
                            buf.append('\r');
                            break;
                        case 'u':
                            int value = 0;
                            for (int j = 0; j < 4; j++) {
                                c = str.charAt(++i);
                                switch (c) {
                                    case '0':
                                    case '1':
                                    case '2':
                                    case '3':
                                    case '4':
                                    case '5':
                                    case '6':
                                    case '7':
                                    case '8':
                                    case '9':
                                        value = (value << 4) + c - '0';
                                        break;
                                    case 'a':
                                    case 'b':
                                    case 'c':
                                    case 'd':
                                    case 'e':
                                    case 'f':
                                        value = (value << 4) + 10 + c - 'a';
                                        break;
                                    case 'A':
                                    case 'B':
                                    case 'C':
                                    case 'D':
                                    case 'E':
                                    case 'F':
                                        value = (value << 4) + 10 + c - 'A';
                                        break;
                                    default:
                                        throw new IllegalArgumentException("Malformed \\uxxxx encoding.");
                                }
                            }
                            buf.append((char) value);
                            break;
                        default:
                            buf.append(c);
                            break;
                    }
                    break;
                default:
                    buf.append(c);
            }
        }
        return buf.toString();
    }

    private StringUtility() {}
}
